/*==============================================================================
 * AUTH: Matthew Baum, 9753095
 * DATE: February-11-2012
 * EDIT: March-04-2012
 * DESC: Represents the Person Data table
 *==============================================================================*/

package SOEN_Phase_2;

// ArrayList Data Structure
import java.util.ArrayList;

// Date/time libraries
import java.util.Calendar;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

// File/IO Libraries
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

// Import Java XML Parsing Libraries
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
 
// Import W3C (WWW Consortium) XML DOM Libraries
import org.w3c.dom.*;

// Java HashTable to hold Person objects
import java.util.HashMap;
import java.util.Collection;
import java.util.Iterator;

// Swing tables
import javax.swing.table.*;


public class PersonTable extends AbstractTableModel
{

    // Table of Person Objects
    private HashMap<Integer, Person> people;

    // Column names for the table
    static final String[] columnNames = {"ID", "Tasks", "Hours"};
    
    private static int latestID = 0;

    // Constructor: init from TaskTable
    public PersonTable(TaskTable taskTable)
    {
        // Init from task table
        initFromTaskTable(taskTable);
    }

    // IMPLEMENTATION from TableModel
    public int getColumnCount() 
    {
        return columnNames.length;
    }

    // IMPLEMENTATION from TableModel
    public int getRowCount() {
        return people.size();
    }

    // IMPLEMENTATION from TableModel
    @Override
    public String getColumnName(int col) {
        return columnNames[col];
    }

    // IMPLEMENTATION: Get class of each object
    @Override
    public Class getColumnClass(int c)
    {
        // Get indexable collection
        Person[] personArray = new Person[people.size()];
        personArray = people.values().toArray(personArray);

        // Get first Task
        Person person = personArray[0];

        // Return value
        Class returnVal = null;

        // SWITCH based on column #
        switch(c)
        {
            // ID
            case 0:
                returnVal = Integer.class;
                break;

            // Name
            //case 1:
              //  returnVal = person.getName().getClass();
                //break;

            // Tasks
            case 1:
                returnVal = String.class;
                break;

            // Hours
            case 2:
                returnVal = Integer.class;
                break;
        }

        return returnVal;
    }

    // IMPLEMENTATION from TableModel
    public Object getValueAt(int row, int col)
    {
        // Get indexable collection
        Person[] personArray = new Person[people.size()];
        personArray = people.values().toArray(personArray);

        // Get Person
        Person person = personArray[row];

        // Return value
        Object returnVal = null;

        // SWITCH based on column #
        switch(col)
        {
            // ID
            case 0:
                returnVal = person.getID();
                break;

            // Name
            //case 1:
                //returnVal = person.getName();
              //  break;

            // Tasks
            case 1:
                String taskList = "";
                ArrayList<Task> tasks = person.getTasks();

                for(int i = 0; i != tasks.size(); ++i)
                {
                    taskList += tasks.get(i).getID();

                    if(i != tasks.size() - 1)
                    {
                        taskList += ", ";
                    }
                }

                returnVal = taskList;
                break;

            // Hours
            case 2:
                returnVal = person.getHours();
                break;
        }

        return returnVal;
    }

    // Init fromtask table
    public final void initFromTaskTable(TaskTable taskTable)
    {
        // Get array of tasks
        Object[] tasks = taskTable.getTasks();

        // Init Hash Map
        people = new HashMap<Integer, Person>(tasks.length * 2);

        // Task temp var
        Task currentTask;

        // Iterate over the tasks
        for(int i = 0; i != tasks.length; ++i)
        {
            currentTask = (Task)tasks[i];

            // Check the hash map to see if the employee ID exists
            if(people.containsKey( currentTask.getAssigneeID() ))
            {
                // Person exists already: Assign him a new task
                people.get(currentTask.getAssigneeID() ).addTask(currentTask);
            }
            else
            {
                // Create a new Person
                Person newPerson = new Person(currentTask.getAssignee(), currentTask.getAssigneeID() );

                // Add task
                newPerson.addTask(currentTask);

                // Insert into hash map
                people.put(newPerson.getID(), newPerson);
            }
        }
    }

    // Write the table to an XML output file
    public void writeToXML(String fname)
    {
        try {

			DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

			// root elements
			Document doc = docBuilder.newDocument();
			Element assign = doc.createElement("WorkersAndTasks");
			doc.appendChild(assign);

            // Get collection of People
            Collection<Person> peopleObjects = people.values();
            Iterator iter = peopleObjects.iterator();
            Person currentPerson;

            while(iter.hasNext() )
            {

                // Get the current person
                currentPerson = (Person)iter.next();

				String tempName = currentPerson.getName();

				Element wrks = doc.createElement("Worker");
				assign.appendChild(wrks);

				//wrks.setAttribute("Name",tempName);


				ArrayList<Task> assignedTasks = currentPerson.getTasks();

				for(int i = 0; i != assignedTasks.size(); ++i)
                {

					// task id elements
                    //as doc.createTextNode() requires a string and not an int
					String id = Integer.toString(assignedTasks.get(i).getID() );

					Element taskId = doc.createElement("TaskID");
					taskId.appendChild(doc.createTextNode(id));
					wrks.appendChild(taskId);
				}


				//total hrs element
                //as doc.createTextNode() requires a string and not an int
				String tTime = Integer.toString( currentPerson.getHours() );


				wrks.setAttribute("TotalHrs", tTime);
			}

			// write the content into xml file
			TransformerFactory transformerFactory = TransformerFactory.newInstance();
			Transformer transformer = transformerFactory.newTransformer();
			
			//set output properties to allow for proper indentation in the xml file
			transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
		    transformer.setOutputProperty(OutputKeys.METHOD, "xml");
		    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
		    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
			

			DOMSource source = new DOMSource(doc);
			StreamResult result = new StreamResult(new File( fname ));
			

			transformer.transform(source, result);
		}
		catch (ParserConfigurationException pce)
		{
			pce.printStackTrace(System.out);
		}
		catch (TransformerException tfe)
		{
			tfe.printStackTrace(System.out);
		}
    }

    // Write the table to a text file
    public void writeToTXT(String dir, String fName)
    {
        // Write data to a TXT file
        printPersonsTable( dir, fName );
    }

    /*
     * Print a text file representation of a Task Table
     * to a specified directory.
     */
    public void printPersonsTable(String dir, String fName)
    {
        // Get today's date: yyyy-MM-dd_HH-mm-ss
        DateFormat prettyFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Calendar cal = Calendar.getInstance();
        String prettyDate = prettyFormat.format( cal.getTime() );

        // Create a File object
        File outputFile = new File(dir, fName);

        // Calculate Table column WIDTH
        int[] columnWidths = calculatePersonsTableColumnWidths();

        // Calculate total table WIDTH
        int tableWidth = calculatePersonsTableWidth(columnWidths);

        try
        {
            // Create Ouput file writer
            FileWriter fw = new FileWriter(outputFile);

            // Write a title
            fw.write("\nPERSONS TABLE:\n\n");

            // Write out table header
            fw.write( printPersonsTableHeading(columnWidths) );

            // Get collection of People
            Collection<Person> peopleObjects = people.values();
            Iterator iter = peopleObjects.iterator();
            Person currentPerson;

            // Write out table rows
            while(iter.hasNext() )
            {
                currentPerson = (Person)iter.next();
                fw.write( printPersonsTableRow(columnWidths, currentPerson) );
            }

            // Write out table footer
            fw.write( printPersonsTableFooter(tableWidth, prettyDate) );

            // Close file writer
            fw.close();
        }
        catch( IOException IOe )
        {
            System.out.println("File Output Error!");
			IOe.printStackTrace(System.out);
        }
    }
    
    // Get a person ID if exist otherwise generate new ID
    public int getPersonID(String personName)
    {
        int personID = getIDByName(personName);

        if (personID < 0)
        {
            // Try to generate an ID for new user
            latestID++;
            personID = latestID;
            while (!(tryRegisterPerson(personName, personID)))
            {
                latestID++;
                personID = latestID;
            }
            // Retrun the new ID for a new person
            return personID;
        }
        // Retrun the ID of an existing person name
        else
        {
            return personID;
        }
    }
    
    // Method check if newName is already exist,
    // it returns person ID otherwise return -1
    public int getIDByName(String newName)
    {
        String tempName = "";
        Collection<Person> peopleObjects = people.values();
        Iterator iter = peopleObjects.iterator();
        Person currentPerson;
        
        // Iterate over person list
        while(iter.hasNext() )
        {
            currentPerson = (Person)iter.next();
            tempName = currentPerson.getName();
            
            // If name is exist return its ID
            if (tempName.equalsIgnoreCase(newName))
            {
                return (currentPerson.getID());
            }  
        }
        // ID does not exist return -1
        return (-1);
    }

    /*
     * Calculate the width of each column for the output table, also modify
     * input integer to store total width
     */
    private int[] calculatePersonsTableColumnWidths()
    {
        /*
         * Create array to hold column widths
         * Initial column values = column titles + 2 * CONST_TAB (each side).
         *
         * COLUMN NAMES:
         * - NAME
         * - TASKS
         * - HOURS
         *
         */
        int padding = DataHelpers.CONST_TAB_WIDTH * 2;
        int[] columnWidths = { 4, 5, 5 };

        /*
         * Loop through the array of all tasks, and find the max length of each
         * column
         */
        // Get collection of People
        Collection<Person> peopleObjects = people.values();
        Iterator iter = peopleObjects.iterator();
        Person currentPerson;

        // Write out table rows
        while(iter.hasNext() )
        {
            // Get next person
            currentPerson = (Person)iter.next();

            // Check Name
            if(currentPerson.getName().length() > columnWidths[0])
            {
                columnWidths[0] = currentPerson.getName().length();
            }

            // Check Task Array
            /*
            if(people[i].title.length() > columnWidths[1])
            {
                columnWidths[1] = people[i].title.length();
            }
             */

            // Check Total Hours
            if(Integer.toString(currentPerson.getHours() ).length() > columnWidths[2])
            {
                columnWidths[2] = Integer.toString(currentPerson.getHours() ).length();
            }
        }

        // Add PADDING to column widths, and calculate total width
        for(int i = 0; i!= columnWidths.length; ++i)
        {
            columnWidths[i] += padding;
        }

        // Return the calculated widths
        return columnWidths;
    }

    // Calculate total table width
    private int calculatePersonsTableWidth(int[] columnWidths)
    {
        int totalWidth = 0;

        // Add all column widths
        for(int i = 0; i!= columnWidths.length; ++i)
        {
            totalWidth += columnWidths[i];
        }

        // Add space for column separators ( '|' )
        totalWidth += columnWidths.length + 1;

        return totalWidth;
    }

    // Print a heading for the task table
    private static String printPersonsTableHeading(int[] columnWidths)
    {
        String header = "|";

        // Fill the TOP of the header
        for(int i = 0; i != columnWidths.length; ++i)
        {
            header += DataHelpers.printChars('=', columnWidths[i]) + '|';
        }

        // FILL HEADER CONTENT
        header += "\n|";
        header += DataHelpers.padString("NAME", columnWidths[0]) + '|';
        header += DataHelpers.padString("TASKS", columnWidths[1]) + '|';
        header += DataHelpers.padString("HOURS", columnWidths[2]) + '|';
        header += "\n|";

        // Fill the BOTTOM of the header
        for(int i = 0; i != columnWidths.length; ++i)
        {
            header += DataHelpers.printChars('=', columnWidths[i]) + '|';
        }

        return( header );
    }

    // Print a row in the task table
    private static String printPersonsTableRow(int[] columnWidths, Person person)
    {
        String entry = "\n|";

        // FILL ENTRY CONTENT
        // Print assignee name
        entry += DataHelpers.padString(person.getName(), columnWidths[0]) + '|';

        // Print the first assigned task
        if(person.getTasks().size() > 0)
        {
            entry += DataHelpers.padString(Integer.toString(person.getTasks().get(0).getID() ), columnWidths[1]) + '|';
        }

        // IF there are no assiged tasks, print an empty space
        else
        {
            entry += DataHelpers.printChars(' ', columnWidths[1]) + '|';
        }

        // Print assigned hours
        entry += DataHelpers.padString(Integer.toString(person.getHours() ), columnWidths[2]) + '|';

        // Print all other tasks (there may be none)
        if(person.getTasks().size() > 1)
        {
            for(int i = 1; i != person.getTasks().size(); ++i)
            {
                // Go to next line
                entry += "\n|";

                // Fill name column with empty space
                entry += DataHelpers.printChars(' ', columnWidths[0]) + '|';

                // Fill in the task number
                entry += DataHelpers.padString(Integer.toString(person.getTasks().get(i).getID() ), columnWidths[1]) + '|';

                // Fill hours column with empty space
                entry += DataHelpers.printChars(' ', columnWidths[2]) + '|';
            }
        }

        entry += "\n|";

        // Draw a line at the bottom
        for(int i = 0; i != columnWidths.length; ++i)
        {
            entry += DataHelpers.printChars('-', columnWidths[i]) + '|';
        }

        return( entry );
    }

    // Print a footer for the task table
    private String printPersonsTableFooter(int tableWidth, String date)
    {
        String footer = "\n|";
        //String footerComment = "*** END OF TABLE : " + date + " ***";
        String footerComment = "*** END ***";

        // Fill the TOP of the footer
        footer += DataHelpers.printChars(' ', tableWidth - 2) + '|';

        // FILL HEADER CONTENT
        int leftPad = (int)(tableWidth - footerComment.length() - 2) / 2;
        int rightPad = tableWidth - footerComment.length() - 2 - leftPad;

        footer += "\n|" +
                DataHelpers.printChars(' ', leftPad) +
                footerComment +
                DataHelpers.printChars(' ', rightPad) +
                "|\n|";

        // Fill the BOTTOM of the header
        footer += DataHelpers.printChars('=', tableWidth - 2) + '|';

        return( footer );
    }

    // Get people array
    public Object[] getPeople()
    {
        return people.values().toArray();
    }

    // Get a single person based on ID (may be NULL)
    public Person getPersonByID(int ID)
    {
        return people.get(ID);
    }

    // Assign a task to a given person
    public void assignTask(int assigneeID, Task task )
    {
        // Get the assignee
        Person assignee = people.get(assigneeID);

        // Assign the new task
        assignee.addTask(task);
    }

    // Set new assignee ID
    public void modifyAssigneeID(int oldID, int newID)
    {
        // Get the assignee
        Person assignee = people.get(oldID);

        // Assign the new ID
        assignee.setID(newID);
    }

    // Set new Assignee NAME
    public void modifyAssigneeName(int ID, String newName)
    {
        // Get the assignee
        Person assignee = people.get(ID);

        // Assign the new ID
        assignee.setName(newName);
    }

    // Register a new person in the table
    public boolean tryRegisterPerson(String name, int ID)
    {
        // Boolean success result
        boolean success = false;

        // Try to get from table, and make sure the ID does NOT exist!
        Person person = people.get(ID);

        // IF ID is unique, add person
        if(person == null)
        {
            people.put(ID, new Person(name, ID));

            // Set success flag
            success = true;
        }

        // Return success result
        return success;
    }

    // Person valid for assigning tasks?
    public boolean personValidForAssignment(int ID, String Name)
    {
        // Return result
        boolean success = false;

        // Person
        Person p;

        if(people.containsKey(ID))
        {
            p = people.get(ID);

            if(p.getName().equalsIgnoreCase(Name))
            {
                success = true;
            }
        }
        else
        {
            success = true;
        }

        return success;
    }
    
    public boolean modifyPersonsTaskID(int assigneeID, int oldID, int newID)
    {
    	Person pers = people.get(assigneeID);
    	ArrayList<Task> tasks = pers.getTasks();
    	boolean success =  false;
    	
    	//Make sure person does indeed have task with oldID assigned to him.
    	//Then, change the id to newID.
    	for(int i = 0; i < tasks.size(); i++)
    	{
    		if(tasks.get(i).getID() == oldID)
    		{
    			tasks.get(i).setID(newID);
    			success = true;
    		}
    	}
    	return success;
    }

    // Modify the total duration of tasks assigned
    public void modifyPersonsTaskDuration(int assigneeID)
    {
    	// Get person required
        Person person = people.get(assigneeID);

        // Recalculate that person's hours
        person.calculateHours();

        // Fire redisplay event
        fireTableDataChanged();
    }
}
